package com.xiyuan.smartutils;

import com.xiyuan.smartutils.crypto.MD5;
import com.xiyuan.smartutils.filters.Filter;
import com.xiyuan.smartutils.filters.FilterEndsWith;
import com.xiyuan.smartutils.filters.JarFilter;
import com.xiyuan.smartutils.reflect.Reflect;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;

/**
 * JAR包相关工具类
 *
 * @version v1.0.0 @author lgz 2017-8-27 新建与整理
 */

public final class Jars {
    
    private Jars() {}
    
    /**
     * 通过JarEntry获取类名
     *
     * @param jarEntry 类实体
     * @return 类名
     */
    public static String getClassName(JarEntry jarEntry) {// 格式如:org/xxx/xxx/Jars.class
        // 1.去掉.class的后缀，2.把/替换成 .
        String className = jarEntry.getName();
        className = StrUtils.trimRight(className, ".class");
        className = StrUtils.replaceAll(className, "/", ".");
        return className;
    }
    
    /**
     * 查询JAR包中后缀列表
     *
     * @param file     JAR文件
     * @param endsWith 后缀名
     * @return 查询到的实体文件
     */
    public static List<JarEntry> filterEndsWith(JarFile file, String endsWith) {
        return filter(file, new FilterEndsWith(endsWith, true));
    }
    
    /**
     * 查询JAR包中后缀列表
     *
     * @param file JAR文件
     * @return List<JarEntry>
     */
    public static List<JarEntry> filter(JarFile file, Filter<String> filter) {
        List<JarEntry> list = new ArrayList<JarEntry>();
        filter(file, new JarFilter() {
            @Override
            public void process(JarFile jarFile, JarEntry entry) {
                list.add(entry);
            }
            
            @Override
            public boolean accept(JarEntry src) {
                return filter.accept(src.getName());
            }
        });
        return list;
    }
    
    /**
     * 查询JAR包中后缀列表
     *
     * @param jar JAR文件
     */
    public static void filter(File jar, JarFilter filter) {
        if (!isJarFile(jar)) {return;}
        
        try {
            filter(new JarFile(jar), filter);
        }
        catch (Throwable e) {}
    }
    
    /**
     * 获取 class 坐在的jar 的文件路径
     *
     * @param clz
     * @return
     */
    public static String getJarFilePath(Class<?> clz) {
        ProtectionDomain pd = clz.getProtectionDomain();
        String path = null;
        if (pd != null && pd.getCodeSource() != null && pd.getCodeSource().getLocation() != null) {
            // 标准情况：通过 ProtectionDomain 获取
            path = pd.getCodeSource().getLocation().getPath();
        }
        else {
            // 回退方案：通过类资源路径解析
            URL classUrl = clz.getResource("/" + clz.getName().replace('.', '/') + ".class");
            if (classUrl != null) {
                if ("jar".equals(classUrl.getProtocol())) {
                    path = classUrl.getPath();
                }
                else if ("file".equals(classUrl.getProtocol())) {
                    path = new File(path).getParentFile().getPath();
                }
            }
        }
        
//        System.err.println("jarPath: " + path);
        path = Files.toLinuxPath(StrUtils.trim(path));
        path = StrUtils.trimLeft(path, "file:");
        path = StrUtils.trimLeft(path, "jar:");
        path = StrUtils.trimRight(path, "!/");
        path = StrUtils.trimRight(path, "!");
        return path;
    }
    
    // private static
    
    public static List<JarEntry> getFiles(String jarPath, Filter<String> filter) {
        List<JarEntry> list = new ArrayList<>();
        if (StrUtils.isBlank(jarPath)) {
            return list;
        }
        
        
        // || !Files.exists(jarPath)
        try {
            String real_jarPath = jarPath;
            String[] arr = StrUtils.trimRight(jarPath, "!").split("!");
            jarPath = arr[0];
            if (arr.length > 1) {
                String p = null;
                for (int i = 0; i < arr.length; i++) {
                    String entry = StrUtils.trimLeft(arr[i], "/");
                    if (p == null) {
                        p = jarPath;
                        continue;
                    }
                    if (!Files.exists(p)) {
                        break;
                    }
                    String name = MD5.encodeUTF8(p + entry) + ".jar";
                    if (TempFiles.exists(name)) {
                        p = jarPath = TempFiles.getTempDir() + "/" + name;
                        break;
                    }
                    
                    try (JarFile jf = new JarFile(p)) {
                        ZipEntry jar = jf.getEntry(entry);
                        if (jar == null) {
                            throw new RuntimeException("Cannot access jar file or not exists: [" + real_jarPath + "]");
                        }
                        byte[] bytes = IoUtils.getBytes(jf.getInputStream(jar));
                        p = jarPath = TempFiles.addTempFile(bytes, name);
                    }
                }
            }
            
            jarPath = java.net.URLDecoder.decode(jarPath, "UTF-8"); // 处理空格和特殊字符
            // 2. 打开 JAR 文件
            try (JarFile jarFile = new JarFile(jarPath)) {
                // 3. 遍历 JAR 条目
                Enumeration<JarEntry> entries = jarFile.entries();
                while (entries.hasMoreElements()) {
                    JarEntry entry = entries.nextElement();
                    if (filter == null || filter.accept(entry.getName())) {
                        list.add(new JarEntry(entry));
                    }
                }
            }
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
        
        return list;
        
    }
    
    /**
     * 查询JAR包中后缀列表
     *
     * @param jarFile JAR文件
     */
    public static void filter(JarFile jarFile, JarFilter filter) {
        Enumeration<JarEntry> enumeration = jarFile.entries();
        while (enumeration.hasMoreElements()) {
            JarEntry entry = enumeration.nextElement();
            if (filter.accept(entry)) {filter.process(jarFile, entry);}
        }
    }
    
    public static boolean isJarFile(String filePath) {
        if (filePath == null) {return false;}
        filePath = filePath.toLowerCase();
        return filePath.endsWith(".jar") || filePath.endsWith(".zip");
    }
    
    public static boolean isJarFile(File file) {
        return file != null && isJarFile(file.getPath());
    }
    
    /**
     * jar打包压缩文件
     *
     * @param fileDirPath   文件目录路径
     * @param jarExportPath JAR导出路径
     * @throws Exception
     */
    public static void jar(String fileDirPath, String jarExportPath) throws Exception {
        Asserts.nonEmpty(fileDirPath, "文件目录路径");
        Asserts.nonEmpty(jarExportPath, "JAR导出路径");
        
        File fileDir = new File(fileDirPath);
        Asserts.as(fileDir.exists() ? null : "文件目录路径不存在");
        
        // 转化为/方式的绝对路径
        String fileDirCanonicalPath = Files.toLinuxPath(fileDir.getCanonicalPath());
        
        FileOutputStream fos = new FileOutputStream(jarExportPath);
        JarOutputStream jos = new JarOutputStream(fos);
        jos.setLevel(Deflater.BEST_COMPRESSION);
        Files.filter(fileDir, new Filter<File>() {
            @Override
            public boolean accept(File file) {
                if (!(file.isFile() && file.canRead())) {
                    return false;
                }
                
                try {
                    String name = Files.toLinuxPath(file.getCanonicalPath());
                    if (file.isDirectory()) {name = name.substring(fileDirCanonicalPath.length() + 1) + "/";}
                    else {name = name.substring(fileDirCanonicalPath.length() + 1);}
                    JarEntry ze = new JarEntry(name);
                    ze.setSize(file.length());
                    ze.setTime(file.lastModified());
                    jos.putNextEntry(ze);
                    
                    if (ze.isDirectory()) {// 目录
                        jos.closeEntry();
                    }
                    else {// 文件
                        try (InputStream is = java.nio.file.Files.newInputStream(file.toPath())) {
                            IoUtils.putBytes(is, jos);
                        }
                        jos.closeEntry();
                    }
                }
                catch (Exception e) {
                    throw new RuntimeException(e.getMessage(), e);
                }
                return true;
            }
        }, null);
        jos.close();
    }
    
    public static void addJar(String jarPath) throws Exception {
        // 从URLClassLoader类中获取类所在文件夹的方法，jar也可以认为是一个文件夹
        File jarFile = new File(jarPath);
        if (!jarFile.exists()) {
            System.out.println("jar file not found.");
            return;
        }
        
        
        // 获取系统类加载器
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        if (!URLClassLoader.class.isAssignableFrom(classLoader.getClass())) {
            classLoader = ClassLoader.getSystemClassLoader();
        }
        
        // 获取jar文件的url路径
        URL url = jarFile.toURI().toURL();
        // jar路径加入到系统url路径里
        addURLMethod().invoke(classLoader, url);
        
    }
    
    private static Method add_url_method = null;
    
    private static Method addURLMethod() throws Exception {
        if (add_url_method != null) {return add_url_method;}
        
        // 获取类加载器的addURL方法，准备动态调用
        add_url_method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
        Reflect.setAccessible(add_url_method);
        return add_url_method;
    }
}
